#include "ATMOS_MSClient.h"
#include "ATMOS_NetworkConnectionInfo.h"
#include "ATMOS_NetworkManagerClient.h"
#include "ATMOS_MasterServerPacketID.h"
#include "ATMOS_Packet.h"

#include "RakNetTypes.h"
#include "RakPeerInterface.h"

#include <iostream>

using namespace ATMOS;

MSClient::MSClient( bool representsGameServer, const std::string &clientName, 
	const NetworkConnectionInfo &masterServerInfo, int localGamePort ) :
PacketListener(),
mRepresentsGameServer( representsGameServer ),
mClientName( clientName ),
mReadyForGameConnection( false ),
mConnectedToMasterServer( false ),
mLocalGamePort( localGamePort )
{	
	// create network manager client
	NetworkManager *networkManager = new NetworkManagerClient();
	networkManager->addPacketListener( this );
	_setNetworkManager( networkManager );
	// conect to MasterServer
	networkManager->initialize( masterServerInfo );
	mConnectedToMasterServer = true;
	
	registerHandler( API_MASTER_SERVER, &MSClient::onMSPacket );
}

MSClient::~MSClient()
{
	unregisterHandler( API_MASTER_SERVER );

	// unregister from the master server
	sendMSUnregisterClient();

	// remove packe listener
	NetworkManager *networkManager = getNetworkManager();
	networkManager->removePacketListener( this );
	
	// disconnect and shutdown
	networkManager->shutdown();
	delete networkManager;
}

void MSClient::update()
{
	getNetworkManager()->update();
}

const std::vector<std::string> &MSClient::getServersList() const
{
	return mRemoteServers;
}

void MSClient::requestConnectionToGameServer( const std::string &serverGUID )
{
	sendGameServerConnectRequest( serverGUID );
}

void MSClient::onConnectionEstablished( Packet *packet )
{
	mConnectedToMasterServer = true;	
	std::cout << mClientName << ": register with master server" << std::endl;
	// now that we've connected to the master server, send the 
	// packet that will register this MSClient with the MS
	sendMSRegisterClient();
	sendMSRequestGameServerList();
}

void MSClient::onConnectionFailed( Packet *packet )
{
	mConnectedToMasterServer = false;
	throw std::runtime_error( "failed to connect to master server" );
}

void MSClient::sendMSPacket( Packet *packet )
{
	getNetworkManager()->broadcastPacket( packet );
}

void MSClient::sendMSRequestGameServerList()
{
	Packet p( API_MASTER_SERVER, getNetworkManager()->getLocalGUID() );
	p.writeUChar( (unsigned char)ATMOS::MSPID_MS_REQUEST_SERVER_LIST );
	p.writeInt( 0 );
	p.writeInt( 100 );

	sendMSPacket( &p );
}

void MSClient::sendMSRegisterClient()
{
	Packet p( API_MASTER_SERVER, getNetworkManager()->getLocalGUID() );
	p.writeUChar( (unsigned char)ATMOS::MSPID_MS_REGISTER_CLIENT );
	p.writeString( mClientName );
	p.writeInt( mRepresentsGameServer ? 1 : 0 );	
	p.writeInt( mLocalGamePort );
	RakNet::RakNetGUID guid = 
		getNetworkManager()->getRakNetPeerInterface()
			->GetGuidFromSystemAddress( RakNet::UNASSIGNED_SYSTEM_ADDRESS );
	p.writeString( guid.ToString() );

	sendMSPacket( &p );
}

void MSClient::sendMSUnregisterClient()
{	
	Packet p( API_MASTER_SERVER, getNetworkManager()->getLocalGUID() );
	p.writeUChar( (unsigned char)ATMOS::MSPID_MS_UNREGISTER_CLIENT );
	p.writeString( mClientName );
	p.writeInt( mRepresentsGameServer ? 1 : 0 );
	p.writeString( getNetworkManager()->getLocalGUID() );

	sendMSPacket( &p );
}

void MSClient::sendGameServerConnectRequest( const std::string &desiredServerGUID )
{
	Packet p( API_MASTER_SERVER, getNetworkManager()->getLocalGUID() );
	// packet sub-id
	p.writeUChar( (unsigned char)ATMOS::MSPID_MS_GAME_CONNECTION_REQUEST );
	// guid of this client
	p.writeString( getNetworkManager()->getRakNetPeerInterface()->GetMyGUID().ToString() );
	// the name of the server to connect to 
	p.writeString( desiredServerGUID );
	// the local guid
	p.writeString( getNetworkManager()->getLocalGUID() );

	sendMSPacket( &p );
}

void MSClient::sendGameConnectionEstablished( const std::string &connectedServerGUID )
{
	Packet p( API_MASTER_SERVER, 
		getNetworkManager()->getRakNetPeerInterface()->GetGuidFromSystemAddress(
			RakNet::UNASSIGNED_SYSTEM_ADDRESS ).ToString() );
	p.writeUChar( (unsigned char)ATMOS::MSPID_MS_GAME_CONNECTION_ESTABLISHED );
	p.writeString( mClientName );
	p.writeString( connectedServerGUID );
	p.writeString( getNetworkManager()->getLocalGUID() );

	sendMSPacket( &p );
}

void MSClient::sendGameConnectionFailed( const std::string &unconnectedServerGUID )
{
	Packet p( API_MASTER_SERVER, 
		getNetworkManager()->getRakNetPeerInterface()->GetGuidFromSystemAddress(
			RakNet::UNASSIGNED_SYSTEM_ADDRESS ).ToString() );
	p.writeUChar( (unsigned char)ATMOS::MSPID_MS_GAME_CONNECTION_FAILED );
	p.writeString( mClientName );
	p.writeString( unconnectedServerGUID );
	RakNet::RakNetGUID guid = 
		getNetworkManager()->getRakNetPeerInterface()
			->GetGuidFromSystemAddress( RakNet::UNASSIGNED_SYSTEM_ADDRESS );
	p.writeString( guid.ToString() );

	sendMSPacket( &p );

}

void MSClient::onMSPacket( Packet *packet )
{
	MasterServerPacketID mspid = (MasterServerPacketID)packet->readUChar();
	switch( mspid )
	{
	case MSPID_NULL:
		break;
	case MSPID_CL_GAME_CONNECTION_APPROVED:
		onGameConnectionApproved( packet );
		break;
	case MSPID_CL_GAME_CONNECTION_DENIED:
		onGameConnectionDenied( packet );
		break;
	case MSPID_CL_RECEIVE_SERVER_LIST:
		onReceiveServerList( packet );
		break;
	}
}

void MSClient::onGameConnectionApproved( Packet *packet )
{
	mReadyForGameConnection = true;
	mGameConnectionInfo.serverIP = packet->readString();
	mGameConnectionInfo.serverPort = packet->readInt();
	mGameConnectionInfo.clientPort = packet->readInt();
}

void MSClient::onGameConnectionDenied( Packet *packet )
{
	// the game connection request was denied (probably because another
	// client snagged it before the request arrived, or because the
	// server quit the game, or because it timed out)
	// -- so now it's time to try again
}

void MSClient::onReceiveServerList( Packet *packet )
{
	mRemoteServers.clear();
	int numServers = packet->readInt();
	for( int i = 0; i < numServers; ++i )
	{
		mRemoteServers.push_back( packet->readString() );
	}
}

bool MSClient::readyForGameConnection() const
{
	return mReadyForGameConnection;
}

NetworkConnectionInfo MSClient::getGameConnectionInfo() const
{
	return mGameConnectionInfo;
}

bool MSClient::connectedToMasterServer()
{
	return mConnectedToMasterServer;
}

void MSClient::onAtmosPacket( Packet *packet )
{
	switch( packet->getAtmosPacketID() )
	{
	case API_MASTER_SERVER:
		onMSPacket( packet );
		break;
	}
}

void MSClient::onDisconnectionNotification( const std::string &senderGUID ) {}

void MSClient::onAlreadyConnected( const std::string &senderGUID ) {}

void MSClient::onIncompatibleProtocolVersion( const std::string &senderGUID ) {}

void MSClient::onRemoteDisconnectionNotification( const std::string &senderGUID ) {}

void MSClient::onRemoteConnectionLost( const std::string &senderGUID ) {}

void MSClient::onNewIncomingConnection( const std::string &senderGUID ) {}

void MSClient::onConnectionBanned( const std::string &senderGUID ) {}

void MSClient::onConnectionAttemptFailed( const std::string &senderGUID ) {}

void MSClient::onNoFreeIncomingConnections( const std::string &senderGUID ) {}

void MSClient::onInvalidPassword( const std::string &senderGUID ) {}

void MSClient::onConnectionLost( const std::string &senderGUID ) {}

void MSClient::onConnectionRequestAccepted( const std::string &senderGUID )
{
	ATMOS_LOG("MSClient connected to MasterServer.  MS GUID: " << senderGUID);
	sendMSRegisterClient();
	mServerGUID = senderGUID;
}

void MSClient::onUnknownPacket( const std::string &senderGUID ) {}

std::string MSClient::getGUID()
{
	return getNetworkManager()->getLocalGUID();
}